/**
AirwayPathfinder for X-Plane Flight Simulator. Calculates the shortest path between two NavAids. 
Copyright (C) 2011  Nils Buletta

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 **/

package de.buletta.airwaypathfinder;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.prefs.BackingStoreException;
import java.util.prefs.InvalidPreferencesFormatException;
import java.util.prefs.Preferences;

import org.jdesktop.swingx.mapviewer.GeoPosition;
import org.jdesktop.swingx.mapviewer.Waypoint;

/**
 * Holds and loads the preferences and the nav data.
 * 
 * @author Nils Buletta
 * @version 0.1
 * 
 */
public class DataHolder {
	private Preferences prefs;

	private Set<Waypoint> navsSet = new HashSet<>();

	private HashMap<String, ArrayList<EarthNav>> navs = new HashMap<>();
	private ArrayList<EarthNav> earthNavList;
	private HashMap<String, Airport> airports = new HashMap<>();
	private boolean prefsOK = false;
	private boolean dataOK = false;

	public DataHolder() {
		initPreferences();
		prepareData();
	}

	/**
	 * Loads the preferences. Preferences are stored in a node in the userRoot
	 * node and on disk in a folder called pathfinder in the users home
	 * directory.
	 * 
	 * @return {@code true} if the preferences were loaded successfully;
	 *         {@code false} otherwise
	 */
	protected boolean initPreferences() {
		Properties prop = System.getProperties();
		String fileSeparator = prop.getProperty("file.separator");
		String pathfinderHome = prop.getProperty("user.home") + fileSeparator
				+ "pathfinder";

		prefs = Preferences.userRoot().node("/de/buletta/pathfinder");

		// test if all the pathes are set in the preferences
		String earthnavpath = prefs.get("EARTHNAVPATH", "default");
		String awypath = prefs.get("AWYPATH", "default");
		String fixpath = prefs.get("FIXPATH", "default");
		String airportpath = prefs.get("AIRPORTPATH", "default");
		if (earthnavpath.equals("default") || awypath.equals("default")
				|| fixpath.equals("default") || airportpath.equals("default")) {

			// if the pathes are not set try to load them from disk
			try {
				Preferences.importPreferences(new FileInputStream(
						pathfinderHome + fileSeparator
								+ "pathfinder_config.xml"));
			} catch (IOException e) {
				// System.err.println("File Not Found");
				// e.printStackTrace();
				prefsOK = false;
				return false;
			} catch (InvalidPreferencesFormatException e) {
				// System.err.println("Wrong File Format");
				// e.printStackTrace();
				prefsOK = false;
				return false;
			}
		}

		prefsOK = true;
		return true;
	}

	/**
	 * Loads and parses the data from the files containing the navigation data
	 * from X-Plane.
	 * 
	 * @return {@code true} if the preferences were loaded successfully;
	 *         {@code false} otherwise
	 */
	protected boolean prepareData() {
		String line;
		StringBuilder navName;
		StringTokenizer tokenizer;

		if (prefs == null) {
			dataOK = false;
			return false;
		}

		String earthnavpath = prefs.get("EARTHNAVPATH", "default");
		String awypath = prefs.get("AWYPATH", "default");
		String fixpath = prefs.get("FIXPATH", "default");
		String airportpath = prefs.get("AIRPORTPATH", "default");
		if (earthnavpath.equals("default") || awypath.equals("default")
				|| fixpath.equals("default") || airportpath.equals("default")) {
			dataOK = false;
			return false;
		}

		// parse the earth_nav.dat file and extract NDB and VOR
		try (BufferedReader in = new BufferedReader(
				new FileReader(earthnavpath))) {

			// skips first three lines
			for (int i = 0; i < 3; i++) {
				in.readLine();
			}

			while ((line = in.readLine()) != null) {
				if (line.startsWith("2") || line.startsWith("3")) {
					EarthNav nav = new EarthNav();

					tokenizer = new StringTokenizer(line, " ");

					nav.setType(Integer.parseInt(tokenizer.nextToken()));
					nav.setPosition(new GeoPosition(Double
							.parseDouble(tokenizer.nextToken()), Double
							.parseDouble(tokenizer.nextToken())));
					nav.setElevation(Integer.parseInt(tokenizer.nextToken()));
					nav.setFrequency(Integer.parseInt(tokenizer.nextToken()));
					nav.setReceptionRange(Integer.parseInt(tokenizer
							.nextToken()));
					nav.setVariation(Double.parseDouble(tokenizer.nextToken()));
					nav.setIdentifier(tokenizer.nextToken());

					navName = new StringBuilder(tokenizer.nextToken());
					while (tokenizer.hasMoreTokens()) {
						navName.append(" ");
						navName.append(tokenizer.nextToken());
					}

					nav.setName(navName.toString());

					earthNavList = navs.get(nav.getIdentifier());
					if (earthNavList == null) {
						navs.put(nav.getIdentifier(),
								earthNavList = new ArrayList<EarthNav>());
					}
					earthNavList.add(nav);
				}

				if (line.startsWith("13")) {
					EarthNav nav = new EarthNav();

					tokenizer = new StringTokenizer(line, " ");

					nav.setType(Integer.parseInt(tokenizer.nextToken()) - 10);
					nav.setPosition(new GeoPosition(Double
							.parseDouble(tokenizer.nextToken()), Double
							.parseDouble(tokenizer.nextToken())));
					nav.setElevation(Integer.parseInt(tokenizer.nextToken()));
					nav.setFrequency(Integer.parseInt(tokenizer.nextToken()));
					nav.setReceptionRange(Integer.parseInt(tokenizer
							.nextToken()));
					nav.setVariation(Double.parseDouble(tokenizer.nextToken()));
					nav.setIdentifier(tokenizer.nextToken());

					navName = new StringBuilder(tokenizer.nextToken());
					while (tokenizer.hasMoreTokens()) {
						navName.append(" ");
						navName.append(tokenizer.nextToken());
					}

					nav.setName(navName.toString());

					earthNavList = navs.get(nav.getIdentifier());
					if (earthNavList == null) {
						navs.put(nav.getIdentifier(),
								earthNavList = new ArrayList<EarthNav>());
					}
					earthNavList.add(nav);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
			dataOK = false;
			return false;
		}

		// parse the earth_fix.dat file and extract the FIX
		try (BufferedReader in = new BufferedReader(new FileReader(fixpath))) {

			// skip first three lines
			for (int i = 0; i < 3; i++) {
				in.readLine();
			}

			while ((line = in.readLine()) != null) {
				if (!line.startsWith("99")) {
					tokenizer = new StringTokenizer(line, " \t");
					EarthNav nav = new EarthNav();
					nav.setType(11);
					nav.setPosition(new GeoPosition(Double
							.parseDouble(tokenizer.nextToken()), Double
							.parseDouble(tokenizer.nextToken())));
					nav.setIdentifier(tokenizer.nextToken());

					// navs.add(nav);

					earthNavList = navs.get(nav.getIdentifier());
					if (earthNavList == null) {
						navs.put(nav.getIdentifier(),
								earthNavList = new ArrayList<EarthNav>());
					}
					earthNavList.add(nav);
				}
			}

		} catch (IOException e) {
			e.printStackTrace();
			dataOK = false;
			return false;
		}

		// parse the earth_awy.dat file and extract the Airways
		try (BufferedReader in = new BufferedReader(new FileReader(awypath))) {

			// skip first three lines
			for (int i = 0; i < 3; i++) {
				in.readLine();
			}

			while ((line = in.readLine()) != null) {
				if (!line.startsWith("99")) {
					tokenizer = new StringTokenizer(line, " \t");
					Connection con1 = new Connection();
					con1.setIdentifier(tokenizer.nextToken());
					con1.setPosition(Double.parseDouble(tokenizer.nextToken()),
							Double.parseDouble(tokenizer.nextToken()));

					Connection con2 = new Connection();
					con2.setIdentifier(tokenizer.nextToken());
					con2.setPosition(Double.parseDouble(tokenizer.nextToken()),
							Double.parseDouble(tokenizer.nextToken()));

					int layer = Integer.parseInt(tokenizer.nextToken());
					con1.setLayer(layer);
					con2.setLayer(layer);

					// add the connection to the corresponding EarthNav object
					if ((earthNavList = navs.get(con1.getIdentifier())) != null) {
						for (EarthNav earthNav : earthNavList) {
							if (earthNav.getPosition().equals(
									con1.getPosition())) {
								earthNav.addConnection(con2);
								break;
							}
						}
					} else {
						// if one point of the connection doesn't exist create
						// one and add to the list
						EarthNav earthNav = new EarthNav(con1.getPosition()
								.getLatitude(), con1.getPosition()
								.getLongitude());
						earthNav.setIdentifier(con1.getIdentifier());
						earthNav.setType(28);
						earthNav.addConnection(con2);
						earthNavList = new ArrayList<EarthNav>();
						earthNavList.add(earthNav);
						navs.put(earthNav.getIdentifier(), earthNavList);
					}

					// add the connection to the corresponding EarthNav object
					if ((earthNavList = navs.get(con2.getIdentifier())) != null) {
						for (EarthNav earthNav : earthNavList) {
							if (earthNav.getPosition().equals(
									con2.getPosition())) {
								earthNav.addConnection(con1);
								break;
							}
						}
					} else {
						// if one point of the connection doesn't exist create
						// one and add to the list
						EarthNav earthNav = new EarthNav(con2.getPosition()
								.getLatitude(), con2.getPosition()
								.getLongitude());
						earthNav.setIdentifier(con2.getIdentifier());
						earthNav.setType(28);
						earthNav.addConnection(con1);
						earthNavList = new ArrayList<EarthNav>();
						earthNavList.add(earthNav);
						navs.put(earthNav.getIdentifier(), earthNavList);
					}

				}
			}
		} catch (IOException e) {
			e.printStackTrace();
			dataOK = false;
			return false;
		}

		try (BufferedReader in = new BufferedReader(new FileReader(airportpath))) {

			// skip first four lines
			for (int i = 0; i < 4; i++) {
				in.readLine();
			}

			Airport airport;
			GeoPosition position;
			String temp;
			int divider = 0;
			double latitude = 0;
			double longitude = 0;

			while ((line = in.readLine()) != null) {

				if (line.startsWith("1 ")) {
					airport = new Airport();
					tokenizer = new StringTokenizer(line, " ");

					// skip next four tokens, nothing interesting there
					for (int i = 0; i < 4; i++) {
						tokenizer.nextToken();
					}

					airport.setIdentifier(tokenizer.nextToken());

					if ((temp = tokenizer.nextToken()).equals("[X]")) {
						temp = "";
					}

					// reusing StringBuilder navName for airport names
					navName = new StringBuilder(temp);
					while (tokenizer.hasMoreTokens()) {
						navName.append(tokenizer.nextToken());
					}
					airport.setName(navName.toString());

					while (((line = in.readLine()) != null) && !line.equals("")) {
						if (line.startsWith("100")) {
							tokenizer = new StringTokenizer(line, " ");

							// skip next nine tokens, nothing interesting there
							for (int i = 0; i < 9; i++) {
								tokenizer.nextToken();
							}

							latitude += Double.parseDouble(tokenizer
									.nextToken());
							longitude += Double.parseDouble(tokenizer
									.nextToken());
							divider++;

							// skip next seven tokens, nothing interesting there
							for (int i = 0; i < 7; i++) {
								tokenizer.nextToken();
							}

							latitude += Double.parseDouble(tokenizer
									.nextToken());
							longitude += Double.parseDouble(tokenizer
									.nextToken());
							divider++;
						}
					}

					position = new GeoPosition(latitude / divider, longitude
							/ divider);
					airport.setPosition(position);
					airports.put(airport.getIdentifier(), airport);
					latitude = longitude = divider = 0;
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
			dataOK = false;
			return false;
		}

		// create the navsSet to use in the WaypointPainter
		for (ArrayList<EarthNav> a : navs.values()) {
			for (EarthNav n : a) {
				navsSet.add(n);
			}
		}
		dataOK = true;
		return true;
	}

	public boolean savePreferences(Gui g) {
		prefs.put("EARTHNAVPATH", g.earthNavTextField.getText());
		prefs.put("FIXPATH", g.fixTextField.getText());
		prefs.put("AWYPATH", g.awyTextField.getText());
		prefs.put("AIRPORTPATH", g.airportTextField.getText());
		prefs.put("FMSSTOREPATH", g.fmsStoreTextField.getText());

		String savePath = System.getProperties().getProperty("user.home")
				+ System.getProperty("file.separator") + "pathfinder";
		try {
			// create folder if not exists
			File f = new File(savePath);
			if (!f.mkdirs()) {
				if (!f.exists()) {
					prefsOK = false;
					return false;
				}
			}

			// export preferences into pathfinder_config.xml; will be created if
			// not exsits
			prefs.exportNode(new FileOutputStream(savePath
					+ System.getProperty("file.separator")
					+ "pathfinder_config.xml"));
		} catch (IOException e) {
			e.printStackTrace();
			prefsOK = false;
			return false;
		} catch (BackingStoreException e) {
			e.printStackTrace();
			prefsOK = false;
			return false;
		}
		prefsOK = true;
		return true;
	}

	/**
	 * Shows if the preferences were loaded correctly
	 * 
	 * @return {@code true} if the preferences were loaded correctly;
	 *         {@code false} otherwise
	 */
	public boolean isPrefsOK() {
		return this.prefsOK;
	}

	/**
	 * Shows if the nav data was loaded correctly
	 * 
	 * @return {@code true} if the nav data was loaded correctly; {@code false}
	 *         otherwise
	 */
	public boolean isDataOK() {
		return this.dataOK;
	}

	/**
	 * Returns the preferences.
	 * 
	 * @return {@code prefs} - the preferences
	 */
	public Preferences getPrefs() {
		return prefs;
	}

	/**
	 * @return the navsSet
	 */
	public Set<Waypoint> getNavsSet() {
		return navsSet;
	}

	/**
	 * @return the navs
	 */
	public HashMap<String, ArrayList<EarthNav>> getNavs() {
		return navs;
	}

	/**
	 * @return the airports
	 */
	public HashMap<String, Airport> getAirports() {
		return airports;
	}

}
